package sql.query;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.tuple.Pair;
import sql.parser.SyntaxNode;
import sql.parser.SyntaxNodeType;
import sql.ast.Context;
import sql.ast.Node;
import sql.ast.State;

import java.util.*;

public class ObjectQuery {
    private String tableName;
    private SyntaxNode selectNode;
    private Node conditions;

    private SyntaxNode orderByNode;

    private SyntaxNode limitNode;

    private ObjectVisitor objectVisitor;

    public ObjectQuery(ObjectVisitor objectVisitor,String tableName, SyntaxNode selectNode, Node conditions, SyntaxNode orderByNode, SyntaxNode limitNode) {
        this.tableName = tableName;
        this.selectNode = selectNode;
        this.conditions = conditions;
        this.orderByNode = orderByNode;
        this.limitNode = limitNode;
        this.objectVisitor = objectVisitor;
    }

    public String execSingleString(Map<String, Map> args) {
        List<Object> stringList = execStringList(args);
        if(stringList.size() > 0) {
            return String.valueOf(stringList.get(0));
        } else {
            return null;
        }
    }

    public List<Object> execStringList(Map<String, Map> args) {
        List<Object> result = Lists.newArrayList();
        List list = execList(args);
        for (int i = 0; i < list.size(); i++) {
            Map map = (Map) list.get(i);
            Set<String> keySet = map.keySet();
            for (String key : keySet) {
                result.add(map.get(key));
                break;
            }
        }
        return result;
    }

    public List execList(Map<String, Map> args) {
        List<SyntaxNode> columns = selectNode.getChildren();
        List list = objectVisitor.readTable(this.tableName);
        List tempResult = new ArrayList();

        boolean selectFinal = orderByNode == null && limitNode == null;
        for (int i = 0; i < list.size(); i++) {
            Map map = (Map) list.get(i);
            Context context = new Context();
            context.setRow(map);
            context.setParams(args);
            State state = conditions.execute(context);
            if(state == State.True) {
                if(selectFinal) {
                    Map resultItem = select(columns, map);
                    tempResult.add(resultItem);
                } else {
                    tempResult.add(map);
                }
            }
        }
        if(selectFinal) {
            return tempResult;
        }

        //处理排序
        if(orderByNode != null) {
            List<Pair<String,Boolean>> sortSetting = Lists.newArrayList();
            List<SyntaxNode> children = orderByNode.getChildren();
            for (SyntaxNode child : children) {
                List<SyntaxNode> pair = child.getChildren();
                SyntaxNode colNode = pair.get(0);
                SyntaxNode modNode = pair.get(1);
                String colName = colNode.getTokenAsString();
                boolean isAsc = modNode.getNodeType() == SyntaxNodeType.OrderByAsc;
                sortSetting.add(Pair.of(colName, isAsc));
            }

            tempResult.sort((a,b)-> compareValue(sortSetting, a, b));
        }

        if(limitNode != null) {
            List limitResult = Lists.newArrayList();
            String token = limitNode.getTokenAsString();
            int limit = NumberUtils.toInt(token);
            int size = tempResult.size();
            if(limit < size) {
                for (int i = 0; i < limit; i++) {
                    limitResult.add(tempResult.get(i));
                }
                tempResult = limitResult;
            }
        }

        List finalResult = new ArrayList();
        for (int i = 0; i < tempResult.size(); i++) {
            Map jsonObject = (Map) tempResult.get(i);
            Map selectResult = select(columns, jsonObject);
            finalResult.add(selectResult);
        }

        return finalResult;
    }

    private int compareValue(List<Pair<String,Boolean>> sortSetting ,Object a, Object b) {
        for (Pair<String, Boolean> item : sortSetting) {
            String col = item.getKey();
            Boolean asc = item.getValue();
            Number aNum = getValue(a, col);
            Number bNum = getValue(b, col);
            int i = compareNum(aNum, bNum);
            if(i == 0) {
                continue;
            }
            return asc ? i : -i;
        }
        return 0;
    }



    private int compareNum(Number a, Number b) {
        if(a instanceof Long && b instanceof Long) {
            Long longA = (Long) a;
            Long longB = (Long) b;
            if(longA > longB) {
                return 1;
            } else if(longA < longB) {
                return -1;
            } else {
                return 0;
            }
        }
        double doubleA = a.doubleValue();
        double doubleB = b.doubleValue();
        if(doubleA > doubleB) {
            return 1;
        } else if(doubleA < doubleB) {
            return -1;
        } else {
            return 0;
        }
    }
    private  Number getValue(Object o, String col) {
        if(!(o instanceof Map<?,?>)) {
            return 0;
        }
        Map<?, ?> map = (Map<?, ?>) o;
        Object value = map.get(col);
        if(value instanceof Number) {
            return (Number) value;
        } else {
            return 0;
        }
    }

    private Map<String, Object> select(List<SyntaxNode> columns, Map jsonObject) {
        LinkedHashMap resultItem = new LinkedHashMap();
        for (SyntaxNode column : columns) {
            if(column.getSelectNode() != null) {
                Transformer transformer = new Transformer(this.objectVisitor);
                ObjectQuery objectQuery = transformer.transformObjectQuery(column);

                Map<String,Map> args0 = Maps.newHashMap();
                args0.put(this.tableName, jsonObject);
                resultItem.put(column.getAlias(), objectQuery.execSingleString(args0));
            } else {
                String colName = StringUtils.isBlank(column.getAlias()) ? column.getTokenAsString() : column.getAlias();
                if(StringUtils.equals(colName,"*")) {
                    resultItem = new LinkedHashMap(jsonObject);
                } else {
                    if(column.getNodeType() == SyntaxNodeType.Func) {
                        List<SyntaxNode> params = column.getChildren();
                        if(params.size() == 2) {
                            String funcName = column.getTokenAsString();
                            String fieldName = params.get(0).getTokenAsString();
                            String patternIndex = params.get(1).getTokenAsString();

                            Object jsonValue = jsonObject.get(fieldName);
                            if(jsonValue == null) {
                                resultItem.put(colName, null);
                            } else {
                                String newValue = null;
//                                        if(StringUtils.equalsIgnoreCase(funcName,"convert")) {
//                                            newValue = CellPatternManger.convert(jsonValue.toString(), NumberUtils.toInt(patternIndex));
//                                        } else if(StringUtils.equalsIgnoreCase(funcName,"convertlist")) {
//                                            newValue = CellPatternManger.convertList(jsonValue.toString(), NumberUtils.toInt(patternIndex));
//                                        }
                                resultItem.put(colName, newValue);
                            }
                        }
                    } else {
                        String token = column.getTokenAsString();
                        Object jsonValue = jsonObject.get(token);
                        resultItem.put(colName, jsonValue);
                    }
                }
            }
        }
        return resultItem;
    }
}
